package com.hxk.juc.queue;

import java.util.concurrent.*;

/**
 * 阻塞队列 BlockingQueue
 *  当阻塞队列为空时，从阻塞队列获取元素的操作会被阻塞
 *  当阻塞队列满时，向阻塞队列添加元素操作会被阻塞
 *  好处：
 *      开发者不用关心线程的阻塞和线程的唤醒，让开发者把更多的时间放在业务上
 *  juc上提供了7种阻塞队列，实地常用的有3种，如下：
 *      ArrayBlockingQueue：由数组构成的有界阻塞队列
 *      LinkedBlockingQueue：由链表构成的有界（Integer.MAX_VALUE太大，慎用）阻塞队列
 *      SynchronousQueue：单值阻塞队列
 *  有4种阻塞队列的调用方式：
 *      1、有异常：插入（add）、删除（remove）、获取队首元素（element）
 *      2、无异常：插入（offer）、删除（poll）、获取队首元素（peek）
 *      3、阻塞：插入（put）、删除（take）
 *      4、过时：插入（offer(E e,Time,TimeUnit)）、删除（poll(Time,TimeUnit)）
 *  本Case分别用4种方式实现对ArrayBlockingQueue的调用实现
 */
public class BlockingQueueCase {
    public static void main(String[] args) throws Exception {
        //设置ArrayBlockingQueue阻塞队列的长度为3
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        //设置LinkedBlockingQueue阻塞队列的长度为3
        //BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>(3);
        //有异常操作队列
        //addAndRemoveApi(blockingQueue);
        //无异常操作队列
        //offerAndPollApi(blockingQueue);
        //阻塞操作队列
        //putAndTakeApi(blockingQueue);
        //过时操作队列
        //offerAndPollTimeApi(blockingQueue);

        //单值阻塞队列演示
        synchronousQueueDemo();
    }

    /**
     * 单值阻塞队列演示(典型的生产者消费者模式)
     */
    private static void synchronousQueueDemo(){

        SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>();

        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName()+"\t 放入值a");
                synchronousQueue.put("a");
                System.out.println(Thread.currentThread().getName()+"\t 放入值b");
                synchronousQueue.put("b");
                System.out.println(Thread.currentThread().getName()+"\t 放入值c");
                synchronousQueue.put("c");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"T1").start();

        //线程T2每过5秒从单值队列synchronousQueue中取出值
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(5);
                System.out.println(Thread.currentThread().getName()+"\t 取出值"+synchronousQueue.take());
                TimeUnit.SECONDS.sleep(5);
                System.out.println(Thread.currentThread().getName()+"\t 取出值"+synchronousQueue.take());
                TimeUnit.SECONDS.sleep(5);
                System.out.println(Thread.currentThread().getName()+"\t 取出值"+synchronousQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"T2").start();
    }

    /**
     * 有异常：插入（add）、删除（remove）、获取队首元素（element）演示
     * @param blockingQueue
     */
    private static void addAndRemoveApi(BlockingQueue<String> blockingQueue){
        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.add("b"));
        System.out.println(blockingQueue.add("c"));
        //上面已经将队列填满，执行到该句时，会报java.lang.IllegalStateException: Queue full异常
        //System.out.println(blockingQueue.add("d"));

        //获取队列队首的元素
        System.out.println(blockingQueue.element());

        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        //上面已经将队列清空，执行到该句时，会报java.util.NoSuchElementException异常
        //System.out.println(blockingQueue.remove());
    }

    /**
     * 无异常：插入（offer）、删除（poll）、获取队首元素（peek）演示
     * @param blockingQueue
     */
    private static void offerAndPollApi(BlockingQueue<String> blockingQueue){

        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b"));
        System.out.println(blockingQueue.offer("c"));
        //上面已经将队列填满，执行到该句时不会将d加入队列
        System.out.println(blockingQueue.offer("d"));

        //获取队列队首的元素
        System.out.println(blockingQueue.peek());

        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());

        //上面已经将队列清空，执行到该句时获取到的值为null
        System.out.println(blockingQueue.poll());
    }

    /**
     * 阻塞：插入（put）、删除（take）
     * @param blockingQueue
     * @throws Exception
     */
    private static void putAndTakeApi(BlockingQueue<String> blockingQueue) throws Exception{

        blockingQueue.put("a");
        blockingQueue.put("b");
        blockingQueue.put("c");
        //上面已经将队列填满，执行到该句时不会将d加入队列,而是一直阻塞在这里，等待队列有消费，当有消费后将立即加入队列
        //blockingQueue.put("d");

        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        //上面已经将队列清空，执行到该句时线程会一直阻塞在这里，等待队列有生产，当有生产后将立即加入队列
        //System.out.println(blockingQueue.take());
    }

    /**
     * 过时：插入（offer(E e,Time,TimeUnit)）、删除（poll(Time,TimeUnit)）
     * @param blockingQueue
     */
    private static void offerAndPollTimeApi(BlockingQueue<String> blockingQueue) throws Exception{

        blockingQueue.offer("a",2L, TimeUnit.SECONDS);
        blockingQueue.offer("b",2L, TimeUnit.SECONDS);
        blockingQueue.offer("c",2L, TimeUnit.SECONDS);
        //上面已经将队列填满，执行到该句时不会将d加入队列,而是阻塞在这里，等待2秒钟后如果还没有加入队列，则自动放弃操作
        blockingQueue.offer("d",2L, TimeUnit.SECONDS);

        blockingQueue.poll(2L,TimeUnit.SECONDS);
        blockingQueue.poll(2L,TimeUnit.SECONDS);
        blockingQueue.poll(2L,TimeUnit.SECONDS);
        //上面已经将队列清空，执行到该句时线程阻塞在这里，等待2秒钟后如果还没有删除成功，则自动放弃操作
        blockingQueue.poll(2L,TimeUnit.SECONDS);
    }
}
